#include	"ZoneService.h"

#include	<protocols/Common.pb.h>
#include	<protocols/Zone.pb.h>
#include	<IniFile.h>

using namespace Proto::Zone;

struct ActiveAccountTask : public SQL::ITask {
	uint64_t from;
	uint32_t serial;
	std::string account;
	std::string code;
	uint64_t at;
	uint32_t sid;

	ActiveAccountTask(uint64_t from, uint32_t serial, const std::string & account, const std::string & code, uint64_t at, uint32_t sid)
		: from(from)
		, serial(serial)
		, account(account)
		, code(code) 
		, at(at)
		, sid(sid) {}

	virtual void OnRun(SQL::Database & db) override {
		RspActiveAccount rsp;
		rsp.set_account(account);
		rsp.set_at(at);
		rsp.set_sid(sid);

		SQL::SharedCursor cursor = db.Query("SELECT COUNT(*) AS num FROM `zone_active_accounts` WHERE account='" + account + "'");
		if (cursor->Next() && cursor->Get<int>("num") > 0) {
			rsp.set_status(Proto::S_Success);
		} else {
			cursor = db.Query("SELECT * FROM `zone_active_accounts` WHERE code='" + code + "'");
			if (cursor->Next()) {
				if (cursor->Get<bool>("is_used")) {
					rsp.set_status(Proto::S_ECodeUsed);
				} else {
					int ret = db.Exec("UPDATE `zone_active_accounts` SET is_used=1, account='" + account + "' WHERE code='" + code + "' AND is_used=0");
					if (ret > 0) rsp.set_status(Proto::S_Success);
					else rsp.set_status(Proto::S_ECodeUsed);
				}
			} else {
				rsp.set_status(Proto::S_EBadParam);
			}
		}		

		Sender * sender = ZoneService::Instance().Find(from);
		if (sender)	sender->Reply(Proto::RSP_ACTIVEACCOUNT, rsp, serial, true);
	}
};

struct CreateRoleTask : public SQL::ITask {
	uint64_t from;
	uint32_t serial;
	std::string account;
	std::string name;
	uint64_t at;
	uint32_t sid;

	CreateRoleTask(uint64_t f, uint32_t s, const std::string & a, const std::string & n, uint64_t at, uint32_t sid)
		: from(f)
		, serial(s)
		, account(a)
		, name(n)
		, at(at)
		, sid(sid) {}

	virtual void OnRun(SQL::Database & db) override {
		RspCreateRole rsp;
		rsp.set_account(account);
		rsp.set_at(at);
		rsp.set_sid(sid);
		rsp.set_name(name);
		rsp.set_pid(0);

		SQL::SharedCursor cursor = db.Query("SELECT COUNT(*) AS num FROM `zone_names` WHERE used='" + name + "'");
		if (cursor->Next() && cursor->Get<int>("num") > 0) {
			rsp.set_status(Proto::S_ENameUsed);
		} else {
			int affected = db.Exec("INSERT INTO `zone_pids`(`time`, `account`) VALUES(NOW()," + account + ")");
			if (affected > 0) {
				uint64_t pid = (uint64_t)db.LastInsertId();
				rsp.set_pid(pid);

				affected = db.Exec("INSERT INTO `zone_names`(`used`, `user`) VALUES('" + name + "'," + std::to_string(pid) + ")");
				if (affected == 0) {
					rsp.set_status(Proto::S_ENameUsed);
				} else {
					rsp.set_status(Proto::S_Success);
				}
			} else {
				rsp.set_status(Proto::S_EBadParam);
			}
		}		

		Sender * sender = ZoneService::Instance().Find(from);
		if (sender)	sender->Reply(Proto::RSP_CREATEROLE, rsp, serial, true);
	}
};

struct RenameTask : public SQL::ITask {
	uint64_t from;
	uint32_t serial;
	uint64_t pid;
	std::string name;

	RenameTask(uint64_t f, uint32_t s, uint64_t p, const std::string & n)
		: from(f)
		, serial(s)
		, pid(p)
		, name(n) {
	}

	virtual void OnRun(SQL::Database & db) override {
		RspRename rsp;
		rsp.set_pid(pid);
		rsp.set_name(name);

		SQL::SharedCursor cursor = db.Query("SELECT COUNT(*) AS num FROM `zone_names` WHERE used='" + name + "'");
		if (cursor->Next() && cursor->Get<int>("num") > 0) {
			rsp.set_success(false);
		} else {
			int affected = db.Exec("UPDATE `zone_names` SET used='" + name + "' WHERE user=" + std::to_string(pid));
			rsp.set_success(affected > 0);
		}

		Sender * sender = ZoneService::Instance().Find(from);
		if (sender)	sender->Reply(Proto::RSP_RENAME, rsp, serial, true);
	}
};

void ZoneService::OnReceive(Sender * sender, Protocol * msg) {
	int pf = msg->pf;
	uint32_t serial = msg->serial;
	uint64_t from = sender->Id();

	if (pf == Proto::REQ_ACTIVEACCOUNT) {
		auto req = Cast<ReqActiveAccount>(msg);
		if (req) _queue.Push<ActiveAccountTask>(from, serial, req->account(), req->code(), req->at(), req->sid());
	} else if (pf == Proto::REQ_CREATEROLE) {
		auto req = Cast<ReqCreateRole>(msg);
		if (req) _queue.Push<CreateRoleTask>(from, serial, req->account(), req->name(), req->at(), req->sid());
	} else if (pf == Proto::REQ_RENAME) {
		auto req = Cast<ReqRename>(msg);
		if (req) _queue.Push<RenameTask>(from, serial, req->pid(), req->name());
	} else {
		LOG_ERR("[zone]Unsupported protocol received.");
	}
}

bool ZoneService::Initialize(class IniFile & conf) {
	std::string host = conf.Get<std::string>("mysql", "ip", "127.0.0.1");
	int port = conf.Get<int>("mysql", "port", 3306);
	std::string user = conf.Get<std::string>("mysql", "user", "root");
	std::string pswd = conf.Get<std::string>("mysql", "pswd", "root");
	std::string db = conf.Get<std::string>("mysql", "db", "game");

	if (!_queue.Open(host, port, user, pswd, db)) return false;

	std::string listen_ip = conf.Get<std::string>("zone", "ip", "127.0.0.1");
	int listen_port = conf.Get<int>("zone", "port", 10000);
	if (Listen(listen_ip, listen_port) != ENet::Success) {
		LOG_ERR("Failed to listen on %s:%d", listen_ip.c_str(), listen_port);
		return false;
	}

	LOG_INFO("Application zone started ...");
	return true;
}
